S11-11 Vue-项目:mr_vue3_ts_cms2
[TOC]
User
页面布局
<div class="user">
<div class="user-search">
<UserSearch />
</div>
<div class="content">
<UserContent />
</div>
</div>组件:UserSearch

使用组件
<template>
<div class="user">
<div class="user-search">+ <UserSearch /></div>
<div class="content">
<UserContent />
</div>
</div>
</template>
<script setup lang="ts">
+ import UserSearch from './cpns/UserSearch/UserSearch.vue'
import UserContent from './cpns/UserContent/UserContent.vue'
</script>页面布局
注意: 在 element-plus 中允许将多行的el-col放到一个el-row中,配合:span属性,当span满 24 份时会自动换行
<div class="user-search">
<el-form label-width="80px">
<el-row :gutter="120">
<el-col :span="8">
<el-form-item label="用户名">
<el-input placeholder="请输入用户名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="真实姓名">
<el-input placeholder="请输入真实姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="手机号码">
<el-input placeholder="请输入手机号码" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="状态">
+
<el-select class="m-2" placeholder="Select" style="width: 100%">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="创建时间">
+
<el-date-picker
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item class="btns">
<el-button icon="Refresh">重置</el-button>
<el-button icon="Search" type="primary">查询</el-button>
</el-form-item>
</el-form>
</div>element 国际化@
重置、查询按钮
<el-form-item class="btns">
+ <el-button icon="Refresh">重置</el-button> + <el-button icon="Search" type="primary">查询</el-button>
</el-form-item>重置功能
1、绑定表单数据
+
<el-form label-width="80px" :model="searchForm" ref="searchFormRef">
<el-row :gutter="120">
<el-col :span="8">
<el-form-item label="用户名" prop="name">
+ <el-input v-model="searchForm.name" placeholder="请输入用户名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="真实姓名" prop="realname">
+ <el-input v-model="searchForm.realname" placeholder="请输入真实姓名" />
</el-form-item>
</el-col>
</el-row>
</el-form>/* 表单数据 */
const searchForm = reactive({
name: '',
realname: '',
cellphone: '',
enable: 1,
creatAt: []
})2、重置表单
<el-button icon="Refresh" @click="hdlReset">重置</el-button><el-form label-width="80px" :model="searchForm" ref="searchFormRef"></el-form>const searchFormRef = ref<InstanceType<typeof ElForm>>()
/* 重置搜索表单 */
function hdlReset() {
searchFormRef.value?.resetFields()
}3、注意: 如果想让 resetFields 起作用,需要添加 prop 属性
+
<el-form-item label="用户名" prop="name">
<!-- prop="name" -->
<el-input v-model="searchForm.name" placeholder="请输入用户名" />
</el-form-item>查询功能
<el-button icon="Search" type="primary" @click="hdlQuery">查询</el-button>/* 根据搜索项查询 */
function hdlQuery() {
console.log('根据搜索项查询')
}组件:UserContent

使用组件
<template>
<div class="user">
<div class="user-search">
<UserSearch />
</div>
<div class="content">+ <UserContent /></div>
</div>
</template>
<script setup lang="ts">
import UserSearch from './cpns/UserSearch/UserSearch.vue'
+ import UserContent from './cpns/UserContent/UserContent.vue'
</script>页面布局
<div class="user-content">
<div class="header">header</div>
<div class="form">form</div>
<div class="navigation">navigation</div>
</div>表格-请求用户列表数据
1、在 services 中发送网络请求
/* 请求用户列表数据 */
export function postUserList() {
return mrRequest.post({
url: '/users/list',
data: {
offset: 0,
size: 10
}
})
}2、在 pinia 中调用网络请求,并保存返回结果
import { postUserList } from '@/service/main/system'
import { defineStore } from 'pinia'
interface ISystemState {
userList: any[]
totalCount: number
}
const useSystemStore = defineStore('system', {
+ state: (): ISystemState => ({
userList: [],
totalCount: 0
}),
actions: {
/* 请求用户列表数据 */
+ async postUserListAction() {
const res = await postUserList()
this.userList = res.data.list
this.totalCount = res.data.totalCount
}
}
})
export default useSystemStore3、在 UserContent 组件中调用 Action,
import useSystemStore from '@/store/main/system'
const systemStore = useSystemStore()
systemStore.postUserListAction()表格-展示用户列表
1、获取数据
import useSystemStore from '@/store/main/system'
import { storeToRefs } from 'pinia'
const systemStore = useSystemStore()
systemStore.postUserListAction()
/* 获取用户列表 */
+ const { userList, totalCount } = storeToRefs(systemStore)2、使用 el-table 展示数据
<el-table :data="userList" border style="width: 100%">
<el-table-column align="center" type="selection" />
<el-table-column align="center" type="index" label="序号" width="60px" />
<el-table-column align="center" prop="name" label="用户名" width="150px" />
<el-table-column align="center" prop="realname" label="真实姓名" width="150px" />
<el-table-column align="center" prop="cellphone" label="手机号码" width="150px" />
<el-table-column align="center" prop="enable" label="状态" width="60px" />
<el-table-column align="center" prop="createAt" label="创建时间" />
<el-table-column align="center" prop="updateAt" label="更新时间" />
<el-table-column align="center" label="操作" width="150px"></el-table-column>
</el-table>4、调整表格样式-宽度、居中、高度、按钮样式
表格样式-宽度、居中
<el-table-column align="center" type="index" label="序号" width="60px" />
<el-table-column align="center" prop="name" label="用户名" width="150px" />
<el-table-column align="center" prop="realname" label="真实姓名" width="150px" />
<el-table-column align="center" prop="cellphone" label="手机号码" width="150px" />
<el-table-column align="center" prop="enable" label="状态" width="60px"></el-table-column>表格样式-高度
.el-table {
:deep(.el-table__cell) {
padding: 12px 0;
}
}表格样式-按钮样式、给按钮添加图标、样式
<el-table-column align="center" label="操作" width="150px">
<div class="btns">
+
<el-button type="primary" text>
<el-icon><Edit /></el-icon>
<span>编辑</span>
</el-button>
+
<el-button type="danger" text>
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>
</div>
</el-table-column>表格-展示启用
知识点: 作用域插槽
视频: [220715] D064-18 Vue3-(理解)组件插槽-作用域插槽的使用.mp4
<el-table-column align="center" prop="enable" label="状态" width="60px">
+
<template #default="scope">
<div class="enable">
+
<el-button size="small" plain :type="scope.row.enable ? 'primary' : 'danger'">
+ {{ scope.row.enable ? '启用' : '禁用' }}
</el-button>
</div>
</template>
</el-table-column>表格-时间格式化@
知识点: 插件:dayjs
1、安装
npm i dayjs2、封装 dayjs
import dayjs from 'dayjs'
+ import utc from 'dayjs/plugin/utc'
// 继承utc
+ dayjs.extend(utc)
export function formatUTC(utcString: string, format: string = 'YYYY-MM-DD HH:mm:ss') {
+ return dayjs.utc(utcString).format(format)
}3、转成东八区时间
export function formatUTC(utcString: string, format: string = 'YYYY-MM-DD HH:mm:ss') {
+ return dayjs.utc(utcString).utcOffset(8).format(format)
}4、使用封装的方法进行格式化
<script setup lang="ts">
+ import { formatUTC } from '@/utils/format
</script>
<el-table-column align="center" prop="createAt" label="创建时间">
<template #default="scope">{{ formatUTC(scope.row.createAt) }}</template>
</el-table-column>
<el-table-column align="center" prop="updateAt" label="更新时间">
<template #default="scope">{{ formatUTC(scope.row.updateAt) }}</template>
</el-table-column>分页-展示
<div class="navigation">
+
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30]"
small="small"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount"
@size-change="hdlSizeChange"
@current-change="hdlCurrentChange"
/>
</div>/* 分页 */
const currentPage = ref(1)
const pageSize = ref(10)
function hdlSizeChange() {}
function hdlCurrentChange() {}分页-切换页码重新请求数据
1、修改网络请求函数
/* 请求用户列表数据 */
+ export function postUserList(query: any) {
return mrRequest.post({
url: '/users/list',
+ data: query
})
}2、在 store 中调用请求函数
const useSystemStore = defineStore('system', {
state: (): ISystemState => ({
userList: [],
totalCount: 0
}),
actions: {
+ async postUserListAction(query: any) {
const res = await postUserList(query)
this.userList = res.data.list
this.totalCount = res.data.totalCount
}
}
})3、在组件中封装调用请求的函数
/* 发送网络请求 */
function fetchUserList(searchForm: ISearchForm = {}) {
const offset = (currentPage.value - 1) * pageSize.value
const size = pageSize.value
const query = { offset, size }
+ const finalQuery = { ...query, ...searchForm }
systemStore.postUserListAction(finalQuery)
}4、页码、每页大小发生变化时,调用fetchUserListData
/* 分页事件 */
function hdlSizeChange() {
fetchUserList()
}
function hdlPageChange() {
fetchUserList()
}点击查询发送请求
难点: 查询和重置按钮属于UserSearch,而数据请求时发生在UserContent组件中,二者是兄弟组件
知识点:
- defineEmits(string:[]):
返回:emit,定义向外发射的事件
1、发射事件queryClick到外部
const emits = defineEmits(['search-form'])
/* 表单数据 */
const searchForm = reactive<ISearchForm>({
name: '',
realname: '',
cellphone: '',
enable: 1,
createAt: ''
})
/* 根据搜索项查询 */
function hdlQuery() {
emits('search-form', searchForm)
}2、在外部监听事件,并接收数据
<UserSearch @search-form="hdlSearchForm" />3、通过绑定 ref 调用另一个组件中的方法
<UserContent ref="contentRef" />/* 调用UserContent组件中的方法,查询数据 */
const contentRef = ref<InstanceType<typeof UserContent>>()
function hdlSearchForm(searchForm: ISearchForm) {
if (contentRef.value) contentRef.value.fetchUserList(searchForm)
}4、在另一个组件中暴露将被调用的方法,并根据传递的 formData 数据发送请求
defineExpose({ fetchUserList })
/* 发送网络请求 */
function fetchUserList(searchForm: ISearchForm = {}) {
const offset = (currentPage.value - 1) * pageSize.value
const size = pageSize.value
const query = { offset, size }
const finalQuery = { ...query, ...searchForm }
systemStore.postUserListAction(finalQuery)
}点击重置发送请求
1、发射事件resetClick到外部
/* 重置搜索表单 */
function hdlReset() {
;+emits('search-form', {})
searchFormRef.value?.resetFields()
}2、在外部监听事件,并接收数据,重新发送请求
<UserSearch @search-form="hdlSearchForm" />/* 调用UserContent组件中的方法,查询数据 */
const contentRef = ref<InstanceType<typeof UserContent>>()
function hdlSearchForm(searchForm: ISearchForm) {
if (contentRef.value) contentRef.value.fetchUserList(searchForm)
}删除-根据 id 删除数据
1、监听删除按钮点击事件
+
<el-button type="danger" text @click="() => hdlDeleteUser(scope.row.id)">
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>2、添加作用域插槽,获取 scope
<el-table-column align="center" label="操作" width="140px">
+
<template #default="scope">
<div class="btns">
<el-button type="primary" text>
<el-icon><Edit /></el-icon>
<span>编辑</span>
</el-button>
+
<el-button type="danger" text @click="() => hdlDeleteUser(scope.row.id)">
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>
</div>
</template>
</el-table-column>3、根据 id 发送请求删除数据
/* 根据id删除用户 */
function hdlDeleteUser(id: number) {
;+systemStore.delUserByIdAction(id)
}4、在 store 中发送调用请求函数
/* 根据id删除用户 */
async delUserByIdAction(id: number) {
+ await delUserById(id)
ElMessage.success('哈哈,删除成功~')
this.postUserListAction({ offset: 0, size: 5 })
},5、在 service 中定义发送网络请求函数
/* 根据id删除用户 */
export function delUserById(id: number) {
return mrRequest.delete({
url: `/users/${id}`
})
}6、删除成功后重新请求数据
/* 根据id删除用户 */
async delUserByIdAction(id: number) {
await delUserById(id)
ElMessage.success('哈哈,删除成功~')
+ this.postUserListAction({ offset: 0, size: 5 })
},新增用户
1、监听新增按钮点击
<el-button class="btn" type="primary" @click="hdlAddUser">新增用户</el-button>组件:UserModal

使用组件
<div class="user-content">+ <UserModal /></div>
<script setup lang="ts">
+ import UserModal from '../UserModal/UserModal.vue'
</script>页面布局
<div class="user-modal">
<el-dialog v-model="modalVisiable" title="新增用户" width="30%" center>
<span> 表单部分 </span>
<template #footer>
<span class="dialog-footer">
<el-button @click="modalVisiable = false">取消</el-button>
<el-button type="primary" @click="modalVisiable = false">确定</el-button>
</span>
</template>
</el-dialog>
</div>显示、隐藏对话框
1、组件 UserContent 中
const emit = defineEmits(['change-visiable'])
/* 新增用户 */
function hdlAddUser() {
emit('change-visiable')
}2、组件 User 中
<UserContent ref="contentRef" @change-visiable="hdlChangeVisiable" /><UserModal ref="modalRef" />/* 修改对话框是否显示 */
const modalRef = ref<InstanceType<typeof UserModal>>()
function hdlChangeVisiable() {
if (modalRef.value) modalRef.value.changeModalVisiable()
}3、组件 UserModal 中
defineExpose({ changeModalVisiable })
const modalVisiable = ref(false)
/* 修改对话框是否显示 */
function changeModalVisiable() {
modalVisiable.value = true
}表单布局
<div class="form">
<el-form label-position="right" label-width="100px" size="large">
<el-form-item label="用户名" prop="name">
<el-input placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="真实姓名" prop="realname">
<el-input placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="密码" prop="password" show-password>
<el-input placeholder="请输入密码" />
</el-form-item>
<el-form-item label="电话号码" prop="cellphone">
<el-input placeholder="请输入电话号码" />
</el-form-item>
<el-form-item label="选择角色" prop="role">
<el-input placeholder="请选择角色" />
</el-form-item>
<el-form-item label="选择部门" prop="department">
<el-input placeholder="请选择部门" />
</el-form-item>
</el-form>
</div>/* 表单数据 */
const addUserForm = reactive({
name: '',
realname: '',
password: '',
cellphone: '',
roleId: '',
departmentId: ''
})角色和部门数据
注意: 由于角色和部门数据可能会在其他许多页面都有使用,应该提取出来,放在main/main.ts 中
1、在 service 中发送网络请求
/* 获取角色列表 */
export function postRoleLists() {
return mrRequest.post({
url: '/role/list'
})
}
/* 获取部门列表 */
export function postDepartmentLists() {
return mrRequest.post({
url: '/department/list'
})
}2、在 store 中调用网络请求
import { postDepartmentLists, postRoleLists } from '@/service/main/main'
import { defineStore } from 'pinia'
interface IMainState {
roleLists: any[]
departmentLists: any[]
}
const useMainStore = defineStore('main', {
+ state: (): IMainState => ({
roleLists: [],
departmentLists: []
}),
actions: {
async postRoleListsAction() {
+ const res = await postRoleLists()
this.roleLists = res.data.list
},
async postDepartmentListsAction() {
+ const res = await postDepartmentLists()
this.departmentLists = res.data.list
}
}
})
export default useMainStore3、在组件中发起 action
// src\views\Main\Main.vue
/* 发送网络请求 */
mainStore.postRoleListsAction()
mainStore.postDepartmentListsAction()展示角色和部门
1、从 store 中获取数据
/* 获取store中数据 */
const { roleLists, departmentLists } = storeToRefs(mainStore)2、遍历展示数据
<el-form-item label="选择角色" prop="roleId">
<el-select v-model="addUserForm.roleId" class="m-2" placeholder="Select">
<el-option + v-for="item in roleLists" + :key="item.id" + :label="item.name" + :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="选择部门" prop="departmentId">
<el-select v-model="addUserForm.departmentId" class="m-2" placeholder="Select">
<el-option + v-for="item in departmentLists" + :key="item.id" + :label="item.name" + :value="item.id" />
</el-select>
</el-form-item>点击确定添加用户
1、监听按钮点击
<template #footer>
<span class="dialog-footer">
<el-button @click="modalVisiable = false">取消</el-button>
+ <el-button type="primary" @click="hdlAddUser">确定</el-button>
</span>
</template>2、在 service 中发送添加用户的网络请求
/* 新增用户 */
export function addUser(userInfo: any) {
return mrRequest.post({
url: '/users',
data: userInfo
})
}3、在 store 中调用网络请求
/* 新增用户 */
async addUserAction(userInfo: any) {
+ await addUser(userInfo)
}4、在组件中,调用 action,创建新用户
/* 添加用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlAddUser() {
modalVisiable.value =
false +
// 验证表单
formRef.value?.validate((valid: any) => {
if (valid) {
// 验证成功
;+systemStore.addUserAction(addUserForm)
} else {
// 验证失败
ElMessage.error('呜呼,验证失败,请重新来过~')
}
})
}5、新增用户后,重新请求用户数据
/* 新增用户 */
async addUserAction(userInfo: any) {
await addUser(userInfo)
+ this.postUserListAction({ offset: 0, size: 5 })
}编辑用户
1、监听编辑按钮点击
+
<el-button type="primary" text @click="() => hdlEditUser(scope.row)">
<el-icon><Edit /></el-icon>
<span>编辑</span>
</el-button>2、向外暴露事件,并传递数据
const emit = defineEmits(['change-visiable', 'edit-click'])
/* 编辑用户 */
function hdlEditUser(userItem: any) {
emit('edit-click', userItem)
}3、在 User 父组件中,监听edit-click事件,并调用弹出框组件中的方法
<UserContent ref="contentRef" @change-visiable="hdlChangeVisiable" + @edit-click="hdlEditClick" />/* 调用模态组件内函数,修改用户 */
function hdlEditClick(userItem: any) {
if (modalRef.value) modalRef.value.changeModalVisiable(userItem)
}4、在 UserModal 组件中,回显当前编辑的用户
/* 修改对话框是否显示 */
const isEdit = ref(false)
function changeModalVisiable(userItem: any = null) {
modalVisiable.value = true
userId.value = userItem?.id
if (userItem) {
// 编辑状态
isEdit.value = true
// 遍历回显
+ for (const key in userForm) {
+ userForm[key] = userItem[key]
+ }
}
}注意: formData 需要定义为 any 类型
/* 表单数据 */
+ const userForm = reactive<any>({ // reactive<any>
name: '',
realname: '',
password: '',
cellphone: '',
roleId: '',
departmentId: ''
})5、不显示密码表单
- 全局记录 isNew 的值
/* 修改对话框是否显示 */
+ const isEdit = ref(false)
const userInfo = ref()
function changeModalVisiable(userItem: any = null) {
modalVisiable.value = true
userId.value = userItem?.id
if (userItem) {
// 编辑状态
+ isEdit.value = true
for (const key in userForm) {
userForm[key] = userItem[key]
}
userInfo.value = userForm
} else {
// 新增状态
+ isEdit.value = false
for (const key in userForm) {
userForm[key] = ''
}
userInfo.value = null
}
}- 根据 isNew 的值,显示、隐藏密码表单
<el-form-item v-if="!isEdit" label="密码" prop="password">
<el-input v-model="userForm.password" placeholder="请输入密码" show-password />
</el-form-item>6、在新增用户的情况下,初始化清空所有表单
function changeModalVisiable(userItem: any = null) {
modalVisiable.value = true
userId.value = userItem?.id
if (userItem) {
// 编辑状态
isEdit.value = true
for (const key in userForm) {
userForm[key] = userItem[key]
}
} else {
// 新增状态
isEdit.value = false
+ for (const key in userForm) {
+ userForm[key] = ''
+ }
}
}点击确定编辑用户
1、service
/* 编辑用户 */
export function editUser(id: number, userInfo: any) {
return mrRequest.patch({
url: `/users/${id}`,
data: userInfo
})
}2、store
/* 修改用户 */
async editUserAction(id: number, userInfo: any) {
+ await editUser(id, userInfo)
this.postUserListAction({ offset: 0, size: 5 })
}3、组件
- 保存 userInfo
+ const userInfo = ref()
function changeModalVisiable(userItem: any = null) {
modalVisiable.value = true
userId.value = userItem?.id
if (userItem) {
// 编辑状态
isEdit.value = true
for (const key in userForm) {
userForm[key] = userItem[key]
}
+ userInfo.value = userForm
} else {
// 新增状态
isEdit.value = false
for (const key in userForm) {
userForm[key] = ''
}
+ userInfo.value = null
}
}- 调用 action,执行编辑操作
function hdlSubmitUser() {
modalVisiable.value = false
// 验证表单
formRef.value?.validate((valid: any) => {
if (valid) {
// 验证成功
if (isEdit.value && userInfo.value) {
;+systemStore.editUserAction(userId.value, userInfo) + ElMessage.success('哈哈,修改用户成功~')
} else {
systemStore.addUserAction(userInfo)
ElMessage.success('哈哈,新增用户成功~')
}
} else {
// 验证失败
ElMessage.error('呜呼,验证失败,请重新来过~')
}
})
}Department
组件:PageSearch


使用组件
<template>
<div class="department">+ <PageSearch @search-form="hdlSearchForm" /></div>
</template>
<script setup lang="ts">
+ import PageSearch from './cpns/PageSearch/PageSearch.vue'
</script>页面布局
<div class="page-search">
<el-form label-width="80px" :model="searchForm" ref="searchFormRef">
<el-row :gutter="120">
<el-col :span="8">
+
<el-form-item label="部门名称" prop="name">
<el-input v-model="searchForm.name" placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="8">
+
<el-form-item label="部门领导" prop="leader">
<el-input v-model="searchForm.leader" placeholder="请输入部门领导" />
</el-form-item>
</el-col>
<el-col :span="8">
+
<el-form-item label="创建时间" prop="createAt">
<el-date-picker
v-model="searchForm.createAt"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item class="btns">
<el-button icon="Refresh" @click="hdlReset">重置</el-button>
<el-button icon="Search" type="primary" @click="hdlQuery">查询</el-button>
</el-form-item>
</el-form>
</div>组件:PageContent
使用组件
<template>
<div class="department">
<PageSearch @search-form="hdlSearchForm" />
+ <PageContent ref="contentRef" @change-visiable="hdlChangeVisiable" @edit-click="hdlEditClick" />
</div>
</template>
<script setup lang="ts">
import PageSearch from './cpns/PageSearch/PageSearch.vue'
+ import PageContent from './cpns/PageContent/PageContent.vue'
</script>提取 page 的请求方法
1、service
/* 请求页面列表数据 */
export function postPageList(pageName: string, query: any) {
return mrRequest.post({
url: `/${pageName}/list`,
data: query
})
}2、store
interface ISystemState {
userList: any[]
totalCount: number
+ pageList: any[]
+ pageTotalCount: number
} state: (): ISystemState => ({
userList: [],
totalCount: 0,
+ pageList: [],
+ pageTotalCount: 0
}),
actions: {
/* 统一接口 */
/* 请求页面列表数据 */
+ async postPageListAction(pageName: string, query: any) {
const res = await postPageList(pageName, query)
this.pageList = res.data.list
this.pageTotalCount = res.data.totalCount
},
}3、组件
/* 发送网络请求 */
function fetchPageList(searchForm: any = {}) {
const offset = (currentPage.value - 1) * pageSize.value
const size = pageSize.value
const query = { offset, size }
const finalQuery = { ...query, ...searchForm } + systemStore.postPageListAction('department', finalQuery)
}
fetchPageList()4、组件获取 store 数据
/* 获取用户列表 */
const { pageList, pageTotalCount } = storeToRefs(systemStore)5、组件展示数据
<div class="form">
+
<el-table :data="pageList" border style="width: 100%">
<el-table-column align="center" type="selection" />
<el-table-column align="center" type="index" label="序号" width="60px" />
+ <el-table-column align="center" prop="name" label="部门名称" width="180px" /> +
<el-table-column align="center" prop="leader" label="部门领导" width="180px" /> +
<el-table-column align="center" prop="parentId" label="上级部门" width="150px" />
<el-table-column align="center" prop="createAt" label="创建时间">
<template #default="scope">{{ formatUTC(scope.row.createAt) }}</template>
</el-table-column>
<el-table-column align="center" prop="updateAt" label="更新时间">
<template #default="scope">{{ formatUTC(scope.row.updateAt) }}</template>
</el-table-column>
<el-table-column align="center" label="操作" width="140px">
<template #default="scope">
<div class="btns">
<el-button type="primary" text @click="() => hdlEditItem(scope.row)">
<el-icon><Edit /></el-icon>
<span>编辑</span>
</el-button>
<el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="navigation">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20]"
small="small"
layout="total, sizes, prev, pager, next, jumper"
+
:total="pageTotalCount"
@size-change="hdlSizeChange"
@current-change="hdlPageChange"
/>
</div>查询功能
在父组件中监听查询按钮点击事件,并调用子组件的方法执行查询
<!-- @search-form="hdlSearchForm" -->
<!-- ref="contentRef" -->
<div class="department">+ <PageSearch @search-form="hdlSearchForm" /> + <PageContent ref="contentRef" /></div>/* 调用UserContent组件中的方法,查询数据 */
const contentRef = ref<InstanceType<typeof PageContent>>()
function hdlSearchForm(searchForm: any) {
if (contentRef.value) contentRef.value.fetchPageList(searchForm)
}重置功能
1、在子组件中发送事件
/* 重置搜索表单 */
function hdlReset() {
emits('search-form', {})
searchFormRef.value?.resetFields()
}2、在父组件中监听重置按钮点击事件,并调用子组件的方法执行重置
<PageSearch @search-form="hdlSearchForm" />/* 调用UserContent组件中的方法,查询数据 */
const contentRef = ref<InstanceType<typeof PageContent>>()
function hdlSearchForm(searchForm: any) {
if (contentRef.value) contentRef.value.fetchPageList(searchForm)
}删除功能
1、service
/* 根据id删除用户 */
export function delPageById(pageName: string, id: number) {
return mrRequest.delete({
url: `/${pageName}/${id}`
})
}2、store
/* 根据id删除 */
async delPageByIdAction(pageName: string, id: number) {
await delPageById(pageName, id)
ElMessage.success('哈哈,删除成功~')
this.postPageListAction(pageName, { offset: 0, size: 5 })
},3、组件
<el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>/* 根据id删除用户 */
function hdlDeletePage(id: number) {
systemStore.delPageByIdAction('department', id)
}新增功能
1、在父组件中监听 PageContent 中的点击事件
<PageContent ref="contentRef" @change-visiable="hdlChangeVisiable" /> <PageModal ref="modalRef" />/* 修改对话框是否显示 */
const modalRef = ref<InstanceType<typeof PageModal>>()
function hdlChangeVisiable() {
if (modalRef.value) modalRef.value.changeModalVisiable()
}2、见:组件 PageModal
组件:PageModal
使用组件
<div class="department">
<PageSearch @search-form="hdlSearchForm" />
<PageContent ref="contentRef" @change-visiable="hdlChangeVisiable" @edit-click="hdlEditClick" />
+ <PageModal ref="modalRef" />
</div>修改 PageModal
1、表单数据
/* 表单数据 */
const pageForm = reactive<any>({
name: '',
leader: '',
parentId: ''
})2、模板
<div class="user-modal">
<el-dialog v-model="modalVisiable" :title="isEdit ? '编辑用户' : '新增用户'" width="30%" center>
<div class="form">
<el-form
+
:model="pageForm"
:rules="formRules"
label-position="right"
label-width="100px"
size="large"
ref="formRef"
>
+
<el-form-item label="部门名称" prop="name">
<el-input v-model="pageForm.name" placeholder="请输入部门名称" />
</el-form-item>
+
<el-form-item label="部门领导" prop="realname">
<el-input v-model="pageForm.leader" placeholder="请输入部门领导" />
</el-form-item>
+
<el-form-item label="上级部门" prop="parentId">
<el-select v-model="pageForm.parentId" class="m-2" placeholder="请选择上级部门" style="width: 100%">
<el-option v-for="item in departmentLists" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="modalVisiable = false">取消</el-button>
<el-button type="primary" @click="hdlSubmitUser">确定</el-button>
</span>
</template>
</el-dialog>
</div>3、从 store 获取数据
/* 获取store中数据 */
const { departmentLists } = storeToRefs(mainStore)点击确定创建部门
1、service
/* 新增用户 */
export function addPage(pageName: string, pageInfo: any) {
return mrRequest.post({
url: `/${pageName}`,
data: pageInfo
})
}2、store
/* 新增 */
async addPageAction(pageName: string, pageInfo: any) {
await addPage(pageName, pageInfo)
this.postPageListAction(pageName, { offset: 0, size: 5 })
},3、组件
<el-button type="primary" @click="hdlSubmitUser">确定</el-button>/* 添加、编辑用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlSubmitUser() {
modalVisiable.value = false
pageInfo.value = pageForm
// 验证表单
formRef.value?.validate((valid: any) => {
if (valid) {
// 验证成功
if (isEdit.value) {
console.log('pageId', pageId.value, 'pageInfo', pageInfo.value)
systemStore.editPageAction('department', pageId.value, pageInfo.value)
ElMessage.success('哈哈,修改用户成功~')
} else {
console.log('pageInfo', pageInfo.value) + systemStore.addPageAction('department', pageInfo.value)
ElMessage.success('哈哈,新增用户成功~')
}
} else {
// 验证失败
ElMessage.error('呜呼,验证失败,请重新来过~')
}
})
}编辑功能
1、service
/* 编辑用户 */
export function editPage(pageName: string, id: number, pageInfo: any) {
return mrRequest.patch({
url: `/${pageName}/${id}`,
data: pageInfo
})
}2、store
/* 编辑 */
async editPageAction(pageName: string, id: number, pageInfo: any) {
await editPage(pageName, id, pageInfo)
this.postPageListAction(pageName, { offset: 0, size: 5 })
}3、组件
/* 添加、编辑用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlSubmitUser() {
modalVisiable.value = false
pageInfo.value = pageForm
// 验证表单
formRef.value?.validate((valid: any) => {
if (valid) {
// 验证成功
if (isEdit.value) {
;+systemStore.editPageAction('department', pageId.value, pageInfo.value)
ElMessage.success('哈哈,修改用户成功~')
} else {
systemStore.addPageAction('department', pageInfo.value)
ElMessage.success('哈哈,新增用户成功~')
}
} else {
// 验证失败
ElMessage.error('呜呼,验证失败,请重新来过~')
}
})
}抽取
抽取:PageSearch@
使用组件
import PageSearch from '@/components/PageSearch/PageSearch.vue'配置
1、定义配置
/deparment/config/search.config.ts
const searchConfig = {
formItems: [
{ type: 'input', prop: 'name', label: '部门名称', placeholder: '请输入部门名称' },
{ type: 'input', prop: 'leader', label: '部门领导', placeholder: '请输入部门领导' },
{ type: 'date-picker', prop: 'createAt', label: '创建时间' }
]
}
export default searchConfig2、传递配置
import searchConfig from './config/search.config'<div class="department">
<PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
</div>3、在组件内部接收 searchConfig
export interface IProps {
searchConfig: {
formItems: any[]
}
}
defineProps<IProps>()根据配置渲染模板
1、根据配置,初始化 search 表单列表
/* 表单数据 */
const initForm: any = {}
for (const item of props.searchConfig.formItems) {
initForm[item.prop] = item.initValue ?? ''
}
const searchForm = reactive<any>(initForm)2、遍历配置项,渲染 search
<el-form label-width="80px" :model="searchForm" ref="searchFormRef">
<el-row :gutter="120">
+
<template v-for="item in searchConfig.formItems" :key="item.prop">
<el-col :span="8">
<el-form-item :label="item.label" :prop="item.prop">
+
<template v-if="item.type === 'input'">
<el-input v-model="searchForm[item.prop]" :placeholder="item.placeholder" />
</template>
+
<template v-if="item.type === 'date-picker'">
<el-date-picker
v-model="searchForm[item.prop]"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</template>
</el-form-item>
</el-col>
</template>
</el-row>
<el-form-item class="btns">
<el-button icon="Refresh" @click="hdlReset">重置</el-button>
<el-button icon="Search" type="primary" @click="hdlQuery">查询</el-button>
</el-form-item>
</el-form>2、select 类型
{
type: 'select',
prop: 'enable',
label: '状态',
placeholder: '请选择状态',
options: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
]
}<template v-else-if="item.type === 'select'">
<el-select v-model="searchForm[item.prop]" class="m-2" :placeholder="item.placeholder" style="width: 100%">
+ <el-option v-for="value in item.options" :key="value.value" v-bind="value" />
</el-select>
</template>可选配置
1、labelWidth


抽取:PageContent@
使用组件
import PageContent from '@/components/PageContent/PageContent.vue'header-配置
1、定义配置
const contentConfig = {
pageName: 'department',
header: {
title: '部门列表',
btnTitle: '新增部门'
}
}
export default contentConfig2、传递配置
<template>
<div class="department">
<PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
+
<PageContent
+
ref="contentRef"
+
:content-config="contentConfig"
+
@change-visiable="hdlChangeVisiable"
+
@edit-click="hdlEditClick"
+
/>
</div>
</template>
<script setup lang="ts">
+ import contentConfig from './config/content.config'
</script>3、组件内部接收配置
export interface IProps {
contentConfig: {
pageName: string
header?: {
title?: string
btnTitle?: string
}
}
}
const props = defineProps<IProps>()header-渲染模板
<div class="header">
+
<h3 class="title">{{ contentConfig.header.title }}</h3>
<el-button class="btn" type="primary" @click="hdlAddItem"> + {{ contentConfig.header.btnTitle }} </el-button>
</div>table-配置
1、定义配置
const contentConfig = {
pageName: 'department',
header: {
title: '部门列表',
btnTitle: '新增部门'
},
+ formItems: [
{ type: 'selection', label: '选择', width: '50px' },
{ type: 'index', label: '序号', width: '60px' },
{ type: 'normal', label: '部门名称', prop: 'name', width: '180px' },
{ type: 'normal', label: '部门领导', prop: 'leader', width: '180px' },
{ type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '140px' }
]
}
export default contentConfig带插槽的时间
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },操作类型
{ type: 'handler', label: '操作', width: '140px' }2、传递配置
<PageContent
ref="contentRef"
+
:content-config="contentConfig"
@change-visiable="hdlChangeVisiable"
@edit-click="hdlEditClick"
/>3、组件内部接收配置
export interface IProps {
contentConfig: {
pageName: string
header: {
title: string
btnTitle: string
}
formItems: any[]
}
}
const props = defineProps<IProps>()table-渲染模板
1、基础模板
<div class="form">
<el-table :data="pageList" border style="width: 100%">
<template v-for="item in contentConfig.formItems" :key="item.prop">
+
<template v-if="item.type === 'timer'">
<el-table-column align="center" v-bind="item">
<template #default="scope">{{ formatUTC(scope.row[item.prop]) }}</template>
</el-table-column>
</template>
+
<template v-else-if="item.type === 'handler'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<div class="btns">
<el-button type="primary" text @click="() => hdlEditItem(scope.row)">
<el-icon><Edit /></el-icon>
<span>编辑</span>
</el-button>
<el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>
</div>
</template>
</el-table-column>
</template>
+
<template v-else>
<el-table-column align="center" v-bind="item" />
</template>
</template>
</el-table>
</div>2、时间类型-timer
+
<template v-if="item.type === 'timer'">
<el-table-column align="center" v-bind="item">
<template #default="scope">{{ formatUTC(scope.row[item.prop]) }}</template>
</el-table-column>
</template>3、操作类型-handler
+
<template v-else-if="item.type === 'handler'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<div class="btns">
<el-button type="primary" text @click="() => hdlEditItem(scope.row)">
<el-icon><Edit /></el-icon>
<span>编辑</span>
</el-button>
<el-button type="danger" text @click="() => hdlDeletePage(scope.row.id)">
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-button>
</div>
</template>
</el-table-column>
</template>定制插槽@
1、修改类型为 custom,并指定插槽名
{ type: 'custom', label: '上级部门', prop: 'parentId', width: '150px', slotName: 'parent' },
{ type: 'custom', label: '上级领导', prop: 'leader', width: '150px', slotName: 'leader' },2、在模板中添加具名插槽
<!-- 定制插槽 -->
<template v-else-if="item.type === 'custom'">
<el-table-column align="center" v-bind="item">
<template #default="scope"> + <slot :name="item.slotName" v-bind="scope" :prop="item.prop"></slot> </template>
</el-table-column>
</template>3、使用插槽,自定义数据
<PageContent
ref="contentRef"
:content-config="contentConfig"
@change-visiable="hdlChangeVisiable"
@edit-click="hdlEditClick"
>
+
<template #parent="scope">
+
<div style="color: red">{{ scope.row[scope.prop] }}</div>
</template>
+
<template #leader="scope">
+
<div style="color: green">{{ scope.row[scope.prop] }}</div>
</template>
</PageContent>4、动态决定插槽中的数据
<template #default="scope">
<slot :name="item.slotName" v-bind="scope" + :prop="item.prop"> </slot>
</template><template #parent="scope">
+
<div style="color: red">{{ scope.row[scope.prop] }}</div>
</template>动态 pageName
1、传递的配置中包含 pageName
const contentConfig = {
+ pageName: 'department',
header: {
title: '部门列表',
btnTitle: '新增部门'
},
}export interface IProps {
contentConfig: {
+ pageName: string
header: {
title: string
btnTitle: string
}
formItems: any[]
}
}
const props = defineProps<IProps>()2、根据传递的 pageName 发送请求
/* 发送网络请求 */
function fetchPageList(searchForm: any = {}) {
const offset = (currentPage.value - 1) * pageSize.value
const size = pageSize.value
const query = { offset, size }
const finalQuery =
{ ...query, ...searchForm } + systemStore.postPageListAction(props.contentConfig.pageName, finalQuery)
}
fetchPageList()/* 根据id删除用户 */
function hdlDeletePage(id: number) {
;+systemStore.delPageByIdAction(props.contentConfig.pageName, id)
}抽取:PageModal@
使用组件
import PageModal from '@/components/PageModal/PageModal.vue'配置
1、定义配置
const modalConfig = {
pageName: 'department',
header: {
addTitle: '新建部门',
editTitle: '编辑部门'
},
formItems: [
{ type: 'input', label: '部门名称', prop: 'name', placeholder: '请输入部门名称' },
{ type: 'input', label: '部门领导', prop: 'leader', placeholder: '请输入部门领导' },
{ type: 'select', label: '上级部门', prop: 'parentId', placeholder: '请选择上级部门' }
]
}
export default modalConfig2、传递配置
<div class="department">
<PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
<PageContent
ref="contentRef"
:content-config="contentConfig"
@change-visiable="hdlChangeVisiable"
@edit-click="hdlEditClick"
>
</PageContent>
+ <PageModal :modal-config="modalConfig" ref="modalRef" />
</div>
<script setup lang="ts">
+ import modalConfig from './config/modal.config'
</script>3、接收配置
export interface IProps {
modalConfig: {
pageName: string
header: {
addTitle: string
editTitle: string
}
formItems: any[]
}
}
const props = defineProps<IProps>()渲染模板
1、header
<el-dialog
v-model="modalVisiable"
+ :title="isEdit ? modalConfig.header.editTitle : modalConfig.header.addTitle"
width="30%"
center
>
<div class="form">
...
</el-dialog>2、表单
<el-form :model="pageForm" :rules="formRules" label-position="right" label-width="100px" size="large" ref="formRef">
+
<template v-for="item in modalConfig.formItems" :key="item.prop">
<el-form-item v-bind="item">
+
<template v-if="item.type === 'input'">
<el-input v-model="pageForm[item.prop]" :placeholder="item.placeholder" />
</template>
+
<template v-else-if="item.type === 'select'">
<el-select v-model="pageForm[item.prop]" class="m-2" :placeholder="item.placeholder" style="width: 100%">
<el-option
++
v-for="value in item?.options"
++
:key="value.value"
++
:label="value.label"
++
:value="value.value"
/>
</el-select>
</template>
</el-form-item>
</template>
</el-form>初始化 formData
/* 表单数据 */
const initForm: any = {}
for (const item of props.modalConfig.formItems) {
initForm[item.prop] = item.initValue ?? ''
}
const pageForm = reactive<any>(initForm)设置初始化值

动态 options 数据
0、数据
entireDepartments:

1、初始化 config 中的 options 为空数组
{
type: 'select',
label: '上级部门',
prop: 'parentId',
placeholder: '请选择上级部门',
+ options: []
}2、在传递 config 过程中对 config 进行修改
注意: 需要对 entireDepartments 数据进行转换后才能使用
/* 为modalConfig添加动态options数据 */
const mainStore = useMainStore()
const modalConfigRef = computed(() => {
const { departmentLists } = mainStore
// 1. 整理departmentLists数据
const departments = departmentLists.map((item) => {
return { label: item.name, value: item.id }
})
// 2. 动态添加departments到item.options中
for (const item of modalConfig.formItems) {
if (item.prop === 'parentId') {
item.options.push(...departments)
}
}
return modalConfig
})<PageModal :modal-config="modalConfigRef" ref="modalRef" />动态 pageName
1、传递的配置中包含 pageName
const modalConfig: IModalConfig = {
+ pageName: 'department',
header: {
addTitle: '新建部门',
editTitle: '编辑部门'
},
}export interface IModalConfig {
+ pageName: string
header: {
addTitle: string
editTitle: string
}
formItems: any[]
}
/* define函数 */
export interface IModalProps {
+ modalConfig: IModalConfig
}
const props = defineProps<IModalProps>()2、根据传递的 pageName 发送请求
/* 添加、编辑用户 */
const formRef = ref<InstanceType<typeof ElForm>>()
function hdlSubmitUser() {
modalVisiable.value = false
pageInfo.value = pageForm
// 验证表单
formRef.value?.validate((valid: any) => {
if (valid) {
// 验证成功
if (isEdit.value) {
;+systemStore.editPageAction(props.modalConfig.pageName, pageId.value, pageInfo.value)
ElMessage.success('哈哈,修改用户成功~')
} else {
;+systemStore.addPageAction(props.modalConfig.pageName, pageInfo.value)
ElMessage.success('哈哈,新增用户成功~')
}
} else {
// 验证失败
ElMessage.error('呜呼,验证失败,请重新来过~')
}
})
}Hooks 抽取@
将不需要修改的代码逻辑部分抽取到 Hooks 中
重置、查询功能
import { ref } from 'vue'
// import type PageContent from '@/components/PageContent/PageContent.vue'
function usePageContent() {
/* 调用UserContent组件中的方法,查询、重置数据 */
const contentRef = ref<any>()
function hdlSearchForm(searchForm: any) {
contentRef.value?.fetchPageList(searchForm)
}
return {
contentRef,
hdlSearchForm
}
}
export default usePageContent使用 hooks
const { contentRef, hdlSearchForm } = usePageContent()新增、修改功能
import { ref } from 'vue'
// import type PageModal from '@/components/PageModal/PageModal.vue'
function usePageModal() {
/* 修改对话框是否显示 */
// const modalRef = ref<InstanceType<typeof PageModal>>()
const modalRef = ref<any>()
function hdlChangeVisiable() {
modalRef.value?.changeModalVisiable()
}
/* 调用模态组件内函数,修改用户 */
function hdlEditClick(pageItem: any) {
modalRef.value?.changeModalVisiable(pageItem)
}
return {
modalRef,
hdlChangeVisiable,
hdlEditClick
}
}
export default usePageModal使用 hooks
const { modalRef, hdlChangeVisiable, hdlEditClick } = usePageModal()Role
组件:PageSearch

使用组件
<div class="role">+ <PageSearch /></div>
<script setup lang="ts">
+ import PageSearch from '@/components/PageSearch/PageSearch.vue'
</script>搜索配置
const searchConfig = {
pageName: 'role',
formItems: [
{ type: 'input', label: '角色名称', prop: 'name', placeholder: '请输入角色名称' },
{ type: 'input', label: '权限介绍', prop: 'intro', placeholder: '请输入权限介绍' },
{ type: 'date-picker', label: '创建时间', prop: 'createAt' }
]
}
export default searchConfig使用配置
<template>
<div class="role">+ <PageSearch :search-config="searchConfig" /></div>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
+ import searchConfig from './config/search.config'
</script></template
>组件:PageContent
使用组件
<template>
<div class="role">
<PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
+ <PageContent />
</div>
</template>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
+ import PageContent from '@/components/PageContent/PageContent.vue'
</script>内容配置
const contentConfig = {
pageName: 'role',
header: {
title: '角色列表',
btnTitle: '新增角色'
},
formItems: [
{ type: 'selection', label: '选择', width: '50px' },
{ type: 'index', label: '序号', width: '60px' },
{ type: 'normal', label: '角色名称', prop: 'name', width: '180px' },
{ type: 'normal', label: '权限介绍', prop: 'intro', width: '180px' },
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '140px' }
]
}
export default contentConfig使用配置
<template>
<div class="role">
<PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
<PageContent + :content-config="contentConfig" />
</div>
</template>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
import PageContent from '@/components/PageContent/PageContent.vue'
import searchConfig from './config/search.config'
+ import contentConfig from './config/content.config'
</script>组件:PageModal
使用组件
<template>
<div class="role">
<PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
<PageContent
:content-config="contentConfig"
ref="contentRef"
@change-visiable="hdlChangeVisiable"
@edit-click="hdlEditClick"
/>
+ <PageModal />
</div>
</template>
<script setup lang="ts">
import PageSearch from '@/components/PageSearch/PageSearch.vue'
import PageContent from '@/components/PageContent/PageContent.vue'
+ import PageModal from '@/components/PageModal/PageModal.vue'
</script>配置
const modalConfig = {
pageName: 'role',
header: {
addTitle: '新增角色',
editTitle: '修改角色'
},
formItems: [
{ type: 'input', label: '角色名称', prop: 'name', placeholder: '请输入角色名称' },
{ type: 'input', label: '权限介绍', prop: 'intro', placeholder: '请输入权限介绍' }
]
}
export default modalConfig使用配置
<template>
<div class="role">+ <PageModal :modal-config="modalConfig" ref="modalRef" /></div>
</template>
<script setup lang="ts">
import PageModal from '@/components/PageModal/PageModal.vue'
+ import modalConfig from './config/modal.config'
</script>事件逻辑
// 事件函数
const { contentRef, hdlSearchForm } = usePageContent()
const { modalRef, hdlChangeVisiable, hdlEditClick } = usePageModal()<div class="role">
+ <PageSearch :search-config="searchConfig" @search-form="hdlSearchForm" />
<PageContent
:content-config="contentConfig"
+
ref="contentRef"
+
@change-visiable="hdlChangeVisiable"
+
@edit-click="hdlEditClick"
/>
+ <PageModal :modal-config="modalConfig" ref="modalRef" />
</div>Menu
组件:PageContent

数据:

使用组件
<template>
<div class="menu">+ <PageContent /></div>
</template>
<script setup lang="ts">
+ import PageContent from '@/components/PageContent/PageContent.vue'
</script>配置
const contentConfig = {
pageName: 'menu',
header: {
title: '菜单列表',
btnTitle: '新建菜单'
},
formItems: [
{ type: 'normal', label: '菜单名称', prop: 'name', width: '150px' },
{ type: 'normal', label: '级别', prop: 'type', width: '80px' },
{ type: 'normal', label: '菜单url', prop: 'url', width: '180px' },
{ type: 'normal', label: '菜单icon', prop: 'icon', width: '190px' },
{ type: 'normal', label: '排序', prop: 'sort', width: '80px' },
{ type: 'normal', label: '权限', prop: 'permission', width: '180px' },
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '140px' }
]
}
export default contentConfig使用配置
<template>
<div class="menu">+ <PageContent :content-config="contentConfig" /></div>
</template>
<script setup lang="ts">
import PageContent from '@/components/PageContent/PageContent.vue'
+ import contentConfig from './config/content.config'
</script>菜单子树展开
1、基本使用
注意:当数据中子数据通过 children 区分时,tree-props 属性可以省略
<el-table
:data="pageList"
border
style="width: 100%"
+
row-key="id"
+
:tree-props="{ children: 'chidren', hasChildren: 'hasChildren' }"
></el-table>2、注意:如果想让子菜单树显示,在配置时,不能添加 type: 'normal'
formItems: [
+ { label: '菜单名称', prop: 'name', width: '150px' },
+ { label: '级别', prop: 'type', width: '80px' },
+ { label: '菜单url', prop: 'url', width: '180px' },
+ { label: '菜单icon', prop: 'icon', width: '190px' },
+ { label: '排序', prop: 'sort', width: '80px' },
+ { label: '权限', prop: 'permission', width: '180px' },
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '140px' }
],3、动态定义 row-key
const contentConfig: IContentConfig = {
pageName: 'menu',
header: {
title: '菜单列表',
btnTitle: '新建菜单'
},
formItems: [
...
],
+ childrenTree: {
+ rowKey: 'id',
+ treeProps: {
+ children: 'children',
+ hasChildren: 'hasChildren'
+ }
+ }
}<el-table :data="pageList" border style="width: 100%" + v-bind="contentConfig.childrenTree"></el-table>权限管理

分配权限@
新建角色时分配权限
定义配置
formItems: [
{ type: 'input', label: '角色名称', prop: 'name', placeholder: '请输入角色名称' },
{ type: 'input', label: '权限介绍', prop: 'intro', placeholder: '请输入权限介绍' },
+{ type: 'custom', label: '分配权限', slotName: 'menuList' }
]自定义插槽
<template v-else-if="item.type === 'custom'">
<slot :name="item.slotName"></slot>
</template>请求完整菜单树数据
1、service
/* 获取权限列表 */
export function postMenuLists() {
return mrRequest.post({
url: '/menu/list'
})
}2、store
async postMenuListsAction() {
const res = await postMenuLists()
this.menuLists = res.data.list
}3、组件 Role 中
const mainStore = useMainStore()
const { menuLists } = storeToRefs(mainStore)
const defaultProps = {
children: 'children',
label: 'name'
}<PageModal :modal-config="modalConfig" :other-info="otherInfo" ref="modalRef">
+
<template #menuList>
<el-tree
+
:data="menuLists"
+
show-checkbox
+
node-key="id"
+
:props="defaultProps"
ref="treeRef"
@check="hdlSelectChecked"
/>
</template>
</PageModal>创建角色时带权限
1、监听 check 事件:点击权限树后获取选中项的 id
<el-tree
:data="menuLists"
show-checkbox
node-key="id"
:props="defaultProps"
ref="treeRef"
+
@check="hdlSelectChecked"
/>/* 获取选中的权限节点 */
const otherInfo = ref({})
function hdlSelectChecked(param1: any, param2: any) {
const menuList = [...param2.checkedKeys, ...param2.halfCheckedKeys]
otherInfo.value = { menuList }
}hdlSelectChecked的 2 个参数:

2、传递额外的数据 otherInfo 到 PageModal 组件中
<PageModal :modal-config="modalConfig" + :other-info="otherInfo" ref="modalRef"></PageModal>3、在 PageModal 组件中接收数据
export interface IModalProps {
modalConfig: IModalConfig
+ otherInfo?: any
}
const props = defineProps<IModalProps>()4、合并 otherInfo 和 formData
function hdlSubmitUser() {
modalVisiable.value = false
+ pageInfo.value = { ...pageForm, ...props.otherInfo }
console.log('pageInfo', pageInfo.value)
// 验证表单
formRef.value?.validate((valid: any) => {
if (valid) {
// 验证成功
if (isEdit.value) {
+ systemStore.editPageAction(props.modalConfig.pageName, pageId.value, pageInfo.value)
ElMessage.success('哈哈,修改用户成功~')
} else {
+ systemStore.addPageAction(props.modalConfig.pageName, pageInfo.value)
ElMessage.success('哈哈,新增用户成功~')
}
} else {
// 验证失败
ElMessage.error('呜呼,验证失败,请重新来过~')
}
})
}权限菜单回显
1、绑定 ElTree 的 ref
<el-tree
:data="menuLists"
show-checkbox
node-key="id"
:props="defaultProps"
+
ref="treeRef"
@check="hdlSelectChecked"
/>2、在 Role 组件中定义回调函数,并将其传递给 usePageModal 中,目的是为了获取数据 itemData
+ const { modalRef, hdlChangeVisiable, hdlEditClick } = usePageModal(editCB)
/* 点击编辑,回显权限 */
const treeRef = ref<InstanceType<typeof ElTree>>()
+ function editCB(pageItem: any) {
// console.log('pageItem: ', pageItem.menuList)
nextTick(() => {
const checkedKeys = mapMenuListToIds(pageItem.menuList)
treeRef.value?.setCheckedKeys(checkedKeys)
})
}3、在 Hook 中接收回调函数
function usePageModal(editCB: (pageItem: any) => void) {
/* 修改对话框是否显示 */
// const modalRef = ref<InstanceType<typeof PageModal>>()
const modalRef = ref<any>()
function hdlChangeVisiable() {
modalRef.value?.changeModalVisiable()
}
/* 调用模态组件内函数,修改用户 */
function hdlEditClick(pageItem: any) {
modalRef.value?.changeModalVisiable(pageItem)
+ if (editCB) editCB(pageItem)
}
return {
modalRef,
hdlChangeVisiable,
hdlEditClick
}
}4、定义一个获取数组中 id 的工具函数
/**
* 根据权限列表获取其所有根节点id
* @param menuList 权限列表
* @returns 权限列表的所有根节点id
*/
export function mapMenuListToIds(menuList: any[]) {
const ids: number[] = []
function recurseGetId(menuList: any[]) {
for (const item of menuList) {
if (item.children) {
recurseGetId(item.children)
} else {
ids.push(item.id)
}
}
}
recurseGetId(menuList)
return ids
}5、使用获取到的 id,调用 ElTree 的setCheckedKeys方法,给控件添加初始值
/* 点击编辑,回显权限 */
const treeRef = ref<InstanceType<typeof ElTree>>()
function editCB(pageItem: any) {
// console.log('pageItem: ', pageItem.menuList)
+ nextTick(() => {
+ const checkedKeys = mapMenuListToIds(pageItem.menuList)
+ treeRef.value?.setCheckedKeys(checkedKeys)
})
}新增重置权限菜单
思路: 在新增角色时,同样添加一个 callback,并在setCheckedKeys 时传递一个空数组



按钮权限@
获取用户所有按钮权限
1、定义获取 userMenus 中所有 permissions 的工具函数

2、使用工具函数,在@store/login/login.ts中的loginAction和loadLocalCacheAction中都获取 permissions

根据权限展示按钮
1、在 PageContent 中获取对应的增删改查的权限

2、根据权限展示、隐藏按钮




3、封装权限判断

4、使用封装的 hook

5、如果没有 query 权限,不展示 PageSearch 组件
PageSearch.vue


为 user 页面添加按钮权限





新建编辑删除后重置 page
思路: 由于新建、编辑、删除都会在 systemStore 中执行 action,因此可以通过在 PageContent 页面中监听 store 中是否有执行这些对应的 action 来决定是否要重置 page

在action 执行成功之后再重置 page

Dashboard
顶部数字展示

1、布局

组件:CountCard
使用组件

页面布局

悬浮提示

动态渲染数据
1、组件内定义类型,并设置默认值

2、渲染组件

请求数据
1、service

2、main

3、组件-发起请求

4、组件-从 store 中获取数据

5、遍历数据

数据动画效果
1、动画插件:npm i countup.js
2、使用 countup 添加动画

3、添加人民币符号

组件:ChartCard
使用组件

页面布局
ECharts
API
- echarts
echarts.init(dom, theme?, opt?)echarts.registerMap(mapName, opt | geoJSON)- echartsInstance
echartsInstance.setOption()
安装
依赖包: echarts
安装: pnpm add echarts
基本使用
1、指定 echarts 的容器


2、引入 echarts


封装 Echarts
1、目录结构

组件:BaseEchart


使用组件


组件:PieEchart
1、使用组件


2、使用base-echart组件,并传入option

3、option 配置

4、修改 BaseEchart,使用传递进来的 option

5、统一导出

组件:LineEchart
1、使用组件

2、配置


3、请求商品销量数据
见:动态渲染数据
4、转化请求的商品销量数据

5、传递数据

组件:RoseEchart
1、使用组件

2、页面布局

3、修改 BaseEchart

组件:BarEchart
1、store

2、组件

3、渲染数据

4、配置


组件:MapEchart
1、注册 map

2、页面布局


3、获取城市经纬度工具函数

4、经纬度数据

动态渲染数据
1、service


2、store

3、组件 Dashboard,获取到的数据需要通过 map 转化一下


4、组件 PieEchart

在pie-echart组件中,可以使用computed监听props.pieData数据的变化


5、组件 BaseEchart 中,使用 watchEffect()监听 options 数据的变化,重新设置 echart

页面缩放
1、window 缩放时,echart 也同时跟随缩放

2、页面组件为响应式布局
